using System;
using System.Collections.Generic;
using System.Text;

namespace GoogleCalendarReminder
{
    /// <summary>
    /// A class which allows for the verification, storage, and retrieval of Google account credentials.
    /// </summary>
    public class CredentialManager
    {
        #region Private Data Members

        private static Google.GData.Client.InvalidCredentialsException _exception =
            new Google.GData.Client.InvalidCredentialsException(
            "User credentials are invalid.");

        #endregion

        #region Private Functions

        private static byte[] encrypt(byte[] clearData, byte[] Key, byte[] IV)
        {
            System.IO.MemoryStream ms =
                new System.IO.MemoryStream();
            System.Security.Cryptography.Rijndael alg =
                System.Security.Cryptography.Rijndael.Create();

            alg.Key = Key;
            alg.IV = IV;

            System.Security.Cryptography.CryptoStream cs =
                new System.Security.Cryptography.CryptoStream(
                ms,
                alg.CreateEncryptor(),
                System.Security.Cryptography.CryptoStreamMode.Write);

            cs.Write(clearData, 0, clearData.Length);
            cs.Close();

            return ms.ToArray();
        }

        private static string encrypt(string clearText, string password)
        {
            byte[] clearBytes =
              System.Text.Encoding.Unicode.GetBytes(clearText);

            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                            0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            byte[] encryptedData =
                encrypt(
                clearBytes,
                pdb.GetBytes(32),
                pdb.GetBytes(16));

            return Convert.ToBase64String(encryptedData);
        }

        private static byte[] encrypt(byte[] clearData, string password)
        {
            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                            0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            return encrypt(clearData, pdb.GetBytes(32), pdb.GetBytes(16));

        }

        private static void encrypt(string fileIn, string fileOut, string password)
        {
            System.IO.FileStream fsIn =
                new System.IO.FileStream(
                fileIn,
                System.IO.FileMode.Open,
                System.IO.FileAccess.Read);
            System.IO.FileStream fsOut =
                new System.IO.FileStream(
                fileOut,
                System.IO.FileMode.OpenOrCreate,
                System.IO.FileAccess.Write);

            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                            0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            System.Security.Cryptography.Rijndael alg =
                System.Security.Cryptography.Rijndael.Create();

            alg.Key = pdb.GetBytes(32);
            alg.IV = pdb.GetBytes(16);

            System.Security.Cryptography.CryptoStream cs =
                new System.Security.Cryptography.CryptoStream(
                fsOut,
                alg.CreateEncryptor(),
                System.Security.Cryptography.CryptoStreamMode.Write);

            int bufferLen = 4096;
            byte[] buffer = new byte[bufferLen];
            int bytesRead;

            do
            {
                bytesRead = fsIn.Read(buffer, 0, bufferLen);
                cs.Write(buffer, 0, bytesRead);
            }
            while (bytesRead != 0);

            cs.Close();
            fsIn.Close();
        }

        private static byte[] decrypt(byte[] cipherData, byte[] Key, byte[] IV)
        {
            System.IO.MemoryStream ms =
                new System.IO.MemoryStream();

            System.Security.Cryptography.Rijndael alg =
                System.Security.Cryptography.Rijndael.Create();

            alg.Key = Key;
            alg.IV = IV;

            System.Security.Cryptography.CryptoStream cs =
                new System.Security.Cryptography.CryptoStream(
                ms,
                alg.CreateDecryptor(),
                System.Security.Cryptography.CryptoStreamMode.Write);

            cs.Write(cipherData, 0, cipherData.Length);
            cs.Close();

            return ms.ToArray();
        }

        private static string decrypt(string cipherText, string password)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);

            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 
                            0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            byte[] decryptedData =
                decrypt(
                cipherBytes,
                pdb.GetBytes(32),
                pdb.GetBytes(16));

            return System.Text.Encoding.Unicode.GetString(decryptedData);
        }

        private static byte[] decrypt(byte[] cipherData, string password)
        {
            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                            0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            return decrypt(cipherData, pdb.GetBytes(32), pdb.GetBytes(16));
        }

        private static void decrypt(string fileIn, string fileOut, string password)
        {
            System.IO.FileStream fsIn =
                new System.IO.FileStream(
                fileIn,
                System.IO.FileMode.Open,
                System.IO.FileAccess.Read);
            System.IO.FileStream fsOut =
                new System.IO.FileStream(
                fileOut,
                System.IO.FileMode.OpenOrCreate,
                System.IO.FileAccess.Write);

            System.Security.Cryptography.PasswordDeriveBytes pdb =
                new System.Security.Cryptography.PasswordDeriveBytes(
                password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                            0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            System.Security.Cryptography.Rijndael alg =
                System.Security.Cryptography.Rijndael.Create();

            alg.Key = pdb.GetBytes(32);
            alg.IV = pdb.GetBytes(16);

            System.Security.Cryptography.CryptoStream cs =
                new System.Security.Cryptography.CryptoStream(
                fsOut,
                alg.CreateDecryptor(),
                System.Security.Cryptography.CryptoStreamMode.Write);

            int bufferLen = 4096;
            byte[] buffer = new byte[bufferLen];
            int bytesRead;

            do
            {
                bytesRead = fsIn.Read(buffer, 0, bufferLen);
                cs.Write(buffer, 0, bytesRead);

            }
            while (bytesRead != 0);

            cs.Close();
            fsIn.Close();
        }

        #endregion

        #region Public Functions

        /// <summary>
        /// Determines whether or not given Google account credentials are valid.
        /// </summary>
        /// <param name="username">The username of the account.</param>
        /// <param name="password">The password of the account.</param>
        /// <returns>true if the credentials are valid; false otherwise.</returns>
        public static bool IsValidCredentials(string username, string password)
        {
            Google.GData.Calendar.CalendarService service =
                new Google.GData.Calendar.CalendarService(Properties.Settings.Default.AppName);

            service.setUserCredentials(username, password);

            try
            {
                service.QueryAuthenticationToken();
                return true;
            }
            catch (Google.GData.Client.InvalidCredentialsException)
            {
                return false;
            }
        }


        /// <summary>
        /// Decrypts and retrieves user credentials.
        /// </summary>
        /// <param name="username">The user name.</param>
        /// <param name="password">The password.</param>
        /// <returns>The stored credentials.</returns>
        /// <exception cref="Google.GData.Client.InvalidCredentialsException">User credentials are invalid.</exception>
        public static GoogleAccountCredentials RetrieveCredentials()
        {
            if (Properties.Settings.Default.Username == string.Empty)
                throw _exception;

            string username = Properties.Settings.Default.Username;
            string password = decrypt(Properties.Settings.Default.Password, username);

            if (IsValidCredentials(username, password)) return new GoogleAccountCredentials(username, password);

            throw _exception;
        }

        /// <summary>
        /// Encrypts and saves user credentials.
        /// </summary>
        /// <param name="username">The user name to save.</param>
        /// <param name="password">The password to encrypt and save.</param>
        /// <exception cref="System.ArgumentNullException">No parameters can have a null value.</exception>
        /// <exception cref="Google.GData.Client.InvalidCredentialsException">User credentials are invalid.</exception>
        public static void SaveCredentials(GoogleAccountCredentials credentials)
        {
            if (credentials == null)
                throw new ArgumentNullException("No parameters can have a null value.");

            if (IsValidCredentials(credentials.Username, credentials.Password))
            {
                string encryptedPassword = encrypt(credentials.Password, credentials.Username);

                Properties.Settings.Default.Username = credentials.Username;
                Properties.Settings.Default.Password = encryptedPassword;
                Properties.Settings.Default.Save();
            }
            else
            {
                throw _exception;
            }
        }

        #endregion
    }
}
